"""
主题: 访问闭包中定义的变量
问题: 你想要扩展函数中的某个闭包，允许它能访问和修改函数的内部变量。
提示 : 
"""

import sys

def sample():
    n = 0
    # Closure function
    def func():
        print('n=', n)

    # Accessor methods for n
    def get_n():
        return n

    def set_n(value):
        nonlocal n
        n = value

    # 访问方法绑定到闭包函数
    func.get_n = get_n
    func.set_n = set_n
    return func

class ClosureInstance:
    '''闭包模拟类'''
    def __init__(self, local=None):
        if local is None:
            local = sys._getframe(1).f_locals

        # Update instance dictionary with callables
        self.__dict__.update((key,value) for key, value in local.items()
                            if callable(value) )
    # Redirect special methods
    def __len__(self):
        return self.__dict__['__len__']()

# Example use
def stack():
    items = []
    def push(item): # NOSONAR
        items.append(item)

    def pop(): # NOSONAR
        return items.pop()

    def __len__(): # NOSONAR
        return len(items)

    return ClosureInstance()

class Stack1:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        return self.items.pop()

    def __len__(self):
        return len(self.items)

def recipe1():
    f = sample()
    f()
    f.set_n(10)
    f()

def recipe2():
    s = stack()
    print(f"{s = }")
    s.push(10)
    s.push(20)
    print(f"{len(s) =}")
    print(f"{s.pop() =}")
    print(f"{s.pop() =}")

def recipe3():   
    from timeit import timeit
    print(
        "> Test involving closures",
        "\ttime =",
        timeit(
            "s = z.stack();s.push(1);s.pop()",
            setup="import __main__ as z",
        ),
        "seconds",
    ) 
    print(
        "> Test involving a class",
        "\ttime =",
        timeit(
            "s = z.Stack1();s.push(1);s.pop()",
            setup="import __main__ as z",
        ),
        "seconds",
    )         

def main():
    print('recipe1'.center(20, '*'))
    recipe1()
    print('recipe2'.center(20, '*'))
    recipe2()
    print('recipe3'.center(20, '*'))
    recipe3()

if __name__ == '__main__':
    main()   